关于软件质量稳定性的方方面面
大话软件质量稳定性
舞动的黑天鹅
蝴蝶效应
墨菲定律
业务高速发展带来的变化
技术债问题
人、流程、文档的博弈
采用不能 cover 的工具和框架
复杂的问题域
质量意识
1. 舞动的黑天鹅
纳西姆・尼古拉斯・塔勒布 (Nassim Nicholas Taleb) 写了两部超级畅销书《随机致富的傻瓜》和《黑天鹅》,并且被誉为 [黑天鹅之父]。何为黑天鹅?
在发现澳大利亚之前,17 世纪之前的欧洲人认为天鹅都是白色的。但随着第一只黑天鹅的出现,这个不可动摇的信念崩溃了。黑天鹅的存在寓意着不可预测的重大稀有事件,它在意料之外并且后果非常严重。
一个黑天鹅事件,具有这三个特点:
(1)稀缺、通常史无前例(rarity)
(2)影响很极端(extreme impact)
(3)虽然它具有意外性,但人的本性促使我们在事后为它的发生编造理由,并且或多或少认为它是可解释和可预测的。
在 IT 系统、社会事件尤其是金融市场,[黑天鹅事件] 屡见不鲜。列举著名的黑天鹅事件如下:
在 1933~1934 年,经历过大萧条之后诞生的罗斯福新政,宣布私人持有黄金为非法,规定以每盎司 20.67 美元将私人黄金上收,然后由国会立法将黄金定价为每盎司 35 美元,美元很快贬值 69%。
2001 年 9 月 11 日上午,美国人刚准备开始一天的工作,恐怖分子劫持了四架飞机撞向美国纽约世贸中心与华盛顿五角大楼。3000 多人在这次黑天鹅事件中丧生,美国的经济此后一度处于瘫痪状态,巨大的经济损失无法用数字来统计。
2013 年 8 月 16 日 11 点 05 分上证指数出现大幅拉升大盘一分钟内涨超 5%。最高涨幅 5.62%,指数最高报 2198.85 点,盘中逼近 2200 点。11 点 44 分上交所称系统运行正常。下午 2 点,光大证券公告称策略投资部门自营业务在使用其独立的套利系统时出现问题。有媒体将此次事件称为 “光大证券乌龙指事件”。
对于乌龙指的事故复盘,触发原因是系统缺陷,策略投资部使用的套利策略系统出现了问题。该策略投资部门系统完全独立于公司其他系统,甚至未置于公司风控系统监控下,因此深层次原因是多级风控体系都未发生作用。
向经验学习的局限性
弗朗西斯・培根就曾经发出这样的警告:当心被我们自己思想的丝线丝丝束缚。无论是 “光大证券乌龙指事件,还是泰坦尼克的沉没,如果业态没有类似的案例,其学习的参考是脆弱的,无从学起。即使有业界案例,不同组织,不同公司未必拥有相应的处置经验,那么其实 [自己的思想],[自己的经验] 也是非常有局限性的。我们把自己知道的东西太当回事了警醒地指出:我们把自己知道的东西太当回事了,而不知道的事比知道的事更有意义。只有反常地思考一切,才有可能发现更多 “不知道的事”。
2. 蝴蝶效应
上个世纪 70 年代,美国一个名叫洛伦兹的气象学家在解释空气系统理论时说,亚马逊雨林一只蝴蝶翅膀偶尔振动,也许两周后就会引起美国得克萨斯州的一场龙卷风。蝴蝶效应的意思是初始条件十分微小的变化经过不断放大,对未来状态也许会造成极其巨大的影响。
我们来读一则吕氏春秋,大抵也有这样的例子。[楚之边邑曰卑梁,其处女与吴之边邑处女桑于境上,戏而伤卑梁之处女。卑梁人操其伤子以让吴人,吴人应之不恭,怒,杀而去之。吴人往报之,尽屠其家。卑梁公怒,曰:“吴人焉敢攻吾邑?” 举兵反攻之,老弱尽杀之矣。吴王夷昧闻之,怒,使人举兵侵楚之边邑,克夷而后去之。吴、楚以此大隆。(《吕氏春秋・察微》)]
吕氏春秋里面说个姑娘因游戏起冲突而引发了 2 个国之间的持续战争,比较形象的放大这样一个道理:如不能见微知著,则其后果无法预知。
对 IT 系统而言,对于非预期的错误比如:
非预期 error
非预期的调用抖动
极少数场景下的规则未被正确处理
错误的优惠处理逻辑
未正确设置的营销活动
……
如果不具备快速、智能的感知能力,那么可能影响的用户变多、影响的商户增加、资金损失增加、业务不可用时间变长…..
3. 墨菲定律
“墨菲定律” 是一种心理学效应,是由爱德华・墨菲(Edward A. Murphy)提出的。
主要观点:
任何事都没有表面看起来那么简单;
所有的事都会比你预计的时间长;
会出错的事总会出错;
如果你担心某种情况发生,那么它就更有可能发生。
墨菲定律在生活中屡见不鲜。比如关键时刻掉链子(那些驾考被教练最看好的精英们,往往会多补考 2 次!!!),你出去买爆米花的时候,银幕上偏偏就出现了精彩镜头,所以评论一部电影如何精彩往往会用一个词:无尿点!
对于 IT 系统而言,墨菲定律的例子太多了。
举个例子,小明在做系统迁移,历时半年。小明是一位经验丰富的架构师,他对系统迁移过程中的自校验、核对、切流策略、灰度能力、回滚机制、容错处理都进行了充分的考虑。但是对于老系统的一种流程处理的缺陷未充分考虑备案或者处理方案。想想,半年很快就过去了,去年才发生 1 起这样的特殊规则,我在新系统上完全规避了这个问题… 但是不凑巧,这个特殊规则不约而至,而老系统还未迁移完…
再说一个例子,前公司有一个非常古老的系统,一直活得好好的。但是由于 RPC 调用中有重试机制,在网络异常的情况可能下会被触发。而该系统对于重复请求的机制处理不是很好,导致如果重复了,就需要一个处理机制。而该系统的处理机制在 95% 的情况下是有效的,而网络重发的概率经过经验测算是一亿分之一。看起来论据很充分了,真心是小概率事件。但是随着业务的发展,以及某些未预期的因素(比如某应用超时的几率)增大,则重发的概率也将增大,导致后来这样的问题连续几周都出现了,我们不得不下决心从根本上解决这个问题。
第三个例子,是我们团队的一个亲身经历。某一天有客户投诉,按理说对于该问题的处理预案是有的,并且团队有充分的备份机制。But 我们并未按预期的速度处理好这个问题。原因是团队的一位同学大婚,大家都去迎亲去了,相关同学只能临时把车停到路边,处理问题。当然有人diss说重要链路要随时oncall待命,但实际情况并没有那么理想,毕竟人不是机器。充分考虑到风险,做提前的准备。
由于人类认识的局限性、骄傲心态、问题域的复杂性、不可把握性等因素,导致软件从业人员在处理软件质量稳定性方面如履薄冰,你今天志得意满,明天就可能伤心欲绝。那么软件质量问题的棘手主要有那些因素导致的呢?
我们从黑天鹅事件谈到了蝴蝶效应、墨菲定律。一言以蔽之为,要把软件研发中的质量搞好并非易事。质量是一个综合的大命题,涉及到业务准确性、稳定性、可用性等方面。比如 xx 大促,某厂商几小时无法交易;收藏夹商品丢失;用户享受的优惠不符合预期,营销规则复杂难以解释等。
黑天鹅告诉我们要走出经验主义,不要把自己知道的东西太当回事了,而不知道的事比知道的事更有意义。蝴蝶效应告诉我们要及时感知问题,止损和熔断,避免问题范围扩大包括传染到其它相关领域。墨菲定律告诉我们,你知道的但是忽略的漏掉终会出问题,在黎明破晓前!
4. 业务高速发展带来的变化
那么,我们得思考一下为什么会这样。在 N 年前,就有人想把软件从业者变成像制造工人一样的,不断流水线工作。但是这几乎没什么可能,因为要解决的问题域太复杂。虽然业界有很多规范、标准、套装软件,但是仍然未解决问题之万一。我们来看一下是如何复杂的。
以我们的一个 team 为例,7 个人 1 年做了 400 多个需求。平台能力在不断发展,有人说高速公路换轮胎,有人说飞机飞行换引擎,这样的事情也没少干。大家都知道满足需求,实现业务价值是软件的天职,至于为了更好适应未来发展的平台化能力也好,新特性也好只能在业务发展的过程中做掉。
在这么多需求的过程中,除了技术以外,对于业务包括规则要有深度把握,包括上下游的一些问题。如有评估不到位,问题就大了。分析到设计阶段的缺失,到代码、测试、发布这些阶段可能一如既往的缺失了。早些年,某些系统已经复杂到只有 1-2 个人能搞懂部分了,幸好这些系统今天都完成了拆分和治理。
5. 技术债问题
技术债务是由 Ward Cunningham 在 1992 年的报告中创造的一个比喻,被定义为当我们有意或无意地做了错误的或不理想的技术决策所累积的债务。它和金融债务非常相似。一个人贷款了就会产生债务。如果他定期还款,那么所创建的债务是可以接受的,不会产生进一步的问题。但是,如果他不还款,就会以利息作为惩罚,并随着不还款次数的增加而增加。如果这个人很长一段时间不能支付任何款项,那么应计利息使得他更难以偿还债务。在极端情况下,该人不得不宣布自己破产。
为了快速做业务,采取简单粗暴的方案是大家表示支持的。[临时方案] 的毛病不在于这 2 个月临时了,而在于这个临时上线之后,再无人管了,俗称 [有人生、没人养]。1 年如果做 5 个临时方案,就等于欠了 5 笔债务。欠债并不可怕,怕的是没有偿还计划,或者借口没有时间,或者接口等业务不那么高速发展的时候。吊诡的是好的业务一定是永远都没有时间的,而差的业务确实不用发展了,因为被下线了,或者整个公司 close 了。So,珍惜高速发展的业务,记得去更换引擎,偿还债务,欠的,迟早要还的。 有关技术债,推荐当当史大官人在周评比中名列前茅的文章:当技术宅遇上技术债,我个人觉得是这方面有调皮调性的好文章。
6. 人、流程、文档的博弈
人、流程、制度、文化都是需要的。但面对复杂的问题域,人类有简单化思考的倾向。比如某兄弟把手工修改的代码通过自动生成工具覆盖掉了,这时我们有 2 种选择。1 是通过 2 个目录来区分,1 是搞一个检查工具来做合并前的 check。这些做法都没有问题,但是不要都靠脑子去记住。
一个存在 3 年以上的产品,当新人来到的时候,在第一个月他们总会提一个问题,xx 产品咋采取这样的技术啊;进入慢、一点文档都没有;只能看代码,代码写得还不咋的。当新人成了老人,他们对更新的新人解答的时候,会说我们当年更惨,只有看代码是靠谱的。对了,小强是非常了解某一块的,有问题找他,比看文档有用。我们不得不承认的事实包括:
写多少文档,如何维护 [活文档] 仍是一个大问题
键时刻靠人传、帮、带,靠传承
7. 采用不能 cover 的工具和框架
技术人员有采用新工具、新语言、新开源产品的追求。当全栈、多语言风潮席卷各社区的时候,不罗列一堆名词真心都不好意思出来打招呼。另外还涉及到技术带头人的偏好问题。
某电商的朋友这样讲述了他们的故事:...... 可能更多时候是业务方变动太大,因为创始人不懂技术。然后这种模式没有可借鉴的,因此在业务上一直都是在变动,然后就形成了一种业务混乱的感觉...... 后来,招了 CTO,CTO 是以前做移动语音云平台的,他们都是用数据库 oracle 来做业务,因此来公司后,改第二版时候把大量的业务逻辑转移到数据库里用存储过程来解决,现在的系统就是乱七八糟了。 此为 CTO 经验偏好症!
另外,组织架构、业务架构和技术架构息息相关。银行的某兄弟有一波吐槽:esb 核心的理念还是服务化,但是我们行里实际情况,服务的梳理一开始就没做好,而且从目前的组织架构来说也很难有起色(服务梳理这块主要靠科技,业务参与程度太低),而且所有服务的发布都是通过 esb,造成 esb 这边是个开发热点。而且,服务化要业务参与进来还需要大 boss 那边驱动了~银行的业务很多时候都是打太极,只管各自的一亩三分地...
一般的文章也好,分享也好。多是讲成功的 case,讲讲走过的弯路其实对于广大人民群众是更喜闻乐见的。[雪球服务化实践历程] 这篇文章谈到他们采用 finagle 的情况,颇有启发性。
唐福林演讲中说到:“雪球的 scala 技术团队免不了有一些人员更替,有转产品的,有转管理的,有转去做另外的业务项目的,导致后面的框架升级和二次开发力量严重不足。加上 finagle 自己迭代速度快,向后兼任又差,整个一个 no zuo no die why you try 的感觉”。于是,唐福林个人花了差不多两周的时间,做了一个简单版本 rpc 框架的尝试。得益于在微博做 motan 框架的经验和教训,框架开发很快,开发出来后,拿给整个技术团队做讨论的时候,才发现问题很多:再后来,团队在针对 rpc 框架的接下来需求的讨论过程中,越讨论越觉得方向有一些偏:大家对基础设施需求的重点并不是在 rpc 调用框架,而更多在于:大量的小服务,开发业务逻辑的便捷性,升级基础包的便捷性,单节点的运行状态,数据收集,监控报警的便捷性等等。于是,在未来,会把接下来服务化工作的重点定义成:微服务化,具体来说,就是开发并维护一个满足雪球自己业务需要的微服务容器。
原文链接:developer.51cto.com/art
8. 复杂的问题域
随着业务的发展,对于可用性的诉求也越来越高。比如你曾经是单机房部署、然后演变成同城异机房、最后又发展到异地机房。因为有异地机房,对于 RPC 的路由复杂度就增加了,我得知道是路由到那个机房的;同样的,在开发环境、线下测试环境要模拟几种部署的情况亦是有复杂度的。
再举一个例子,有一个系统作支付链路的规则决策,起初可能就 4 万行代码;后来增加到 8 万,现在又增加到 10 万。代码行增加了,该应用的职责增加了,也可能调用逻辑的运算复杂度。那么如何保持对外 API 的 TPS 不降低,RT 不降低?
每次 release 不仅要完成功能用例的构建,亦要完成性能的测试。
9. 质量意识
意识是很重要的,比如值班 oncall,随时待命。如果没有被叫醒或者打不通电话就比较糟糕了。我们一般还有备份 AB 角机制。著名的朱赟博士有一篇文章就谈了 oncall 的故事,叫 [工程师 oncall 那点事]。
其实经过分析,80% 的线上问题不是那么难的。其本质是未遵循规定动作。比如修改了代码未充分验证。就修改了几行,咋看都不会出错。从内建质量的角度,我们一般有 N 条质量防线,一个问题被遗留到线上,往往有 3 个以上的措施都失守了。
大家知道,航空飞行是马虎不得的。最近看到一篇报纸谈飞行作风问题,我觉得对软件研发质量仍有类比作用和启发效果。
文章提到几点经验,颇有借鉴意义:
不忘初心:严格遵守 SOP(标准操作程序)
减少侥幸心理、增强风险意识
狠抓作风养成
严厉处罚无后果违章,既要结果,又要过程正确
文章深刻的提到,随着飞行经历的增加,许多程序显得啰嗦、许多检查显得多余、以至于越飞程序越少、多做越简化且不把规章当回事...
总之,质量问题就是达摩克利斯之剑高悬于空中。你不注意它,它就会在某一天降落。上述提到的复杂度问题、业务高速发展、技术债、对于工具的掌握能力等都可能为品质埋下了隐患。能不能破,如何破?
求解质量熵
前面是提出问题,为了破解这些个复杂的问题,我们打算从以下几个方面来思考这个问题。如同没有银弹一样,也没有万能解药。
1.运用敏捷思想
什么是敏捷思想,徐毅大大等知名教练、咨询师们必然懂得比我得多,有开山立派的,有翻译多本书籍的,有已服务万千客户的。他们同时还在理论界辛勤耕耘。接地气的申导 (技术团队领导者的软技能) 来成都布道说,敏捷的三个层次。一是思想,二是组织,三是操作方法。
敏捷宣言如是说:
我们一直在实践中探寻更好的软件开发方法,身体力行的同时也帮助他人。由此我们建立了如下价值观:
个体和互动 高于 流程和工具
工作的软件 高于 详尽的文档
客户合作 高于 合同谈判
响应变化 高于 遵循计划
也就是说,尽管右项有其价值,我们更重视左项的价值。
敏捷建模(Agile Modeling,AM)的价值观包括了 XP(Extreme Programming:极限编程)的四个价值观:沟通、简单、反馈、勇气,此外,还扩展了第五个价值观:谦逊。至少我们明白了快速反馈有什么好处,做错了做偏了,可以马上校正。如果瀑布模型式的开发反馈必然是慢的。此前有一位做医疗单机软件的朋友说,他们半年才发布一次,可见这里面提升的空间有多大。
申导提及的第二层组织,我理解有组织层的支持,比如 PMO,项目团队是否按 scrum 等;
第三层操作方法则是持续集成、单元测试、TDD 这些具体的实践。
总结一下:基于敏捷的思想可以快速 release,快速纠正和调整,满足用户的需求。
2. 运用系统化的思想
我理解系统化的思想就是体系、举一反三、通过类比、抽象、层次化等手段挖掘本质。往往出了一个 bug,故障复盘会开了,action 有了,但下次又出错了。究其本质,我觉得两点。一是复盘的内容 focus 在单个事件,对于更体系的影响洞察不够。二是未持续学习,对于问题是不情愿的承担。
谈到学习,想起大卫张 (琅琊访谈录) 引用的一张图。太多人对这种发生的改变持抵抗态度。如同萨提亚改变模型所示,当我们适应了老的状态,遭遇到改变时,我们会经历抵抗、混乱、改变主意、重新上路等过程,最后到达新的状态。此前我们团队通过深度复盘这一实践让鲜血直接喷洒出来,通过大约 1 年的持续联系终使我们对于问题的认知【包括对于故障】,以及担当力都上了一个台阶。
3. 技术债偿还计划
技术债务是由 Ward Cunningham 在 1992 年的报告中创造的一个比喻,被定义为当我们有意或无意地做了错误的或不理想的技术决策所累积的债务。它和金融债务非常相似。一个人贷款了就会产生债务。如果他定期还款,那么所创建的债务是可以接受的,不会产生进一步的问题。但是,如果他不还款,就会以利息作为惩罚,并随着不还款次数的增加而增加。如果这个人很长一段时间不能支付任何款项,那么应计利息使得他更难以偿还债务。在极端情况下,该人不得不宣布自己破产。
前一段在一个讨论中,关于技术债务有 4 个有趣的观点。
由于技术人员水平不行,意识不够,所以越写越烂。
developer 都有一颗积极向上的红心,但是他们不知道如何做才是好的。
上行下效,没有靠谱的 Leader,架构师,大家都是懒惰的,不想去还债。
华为的同学表示,永远的业务压力;其实所有公司都一样滴,赶着交付,那有时间去清偿。
关于债务清偿,总结了务实接地气的几个思路。
构建质量保障体系,让我有勇气动手动刀。比如接口测试、单元测试,通过 CI 持续反馈,这样我有 50% 的勇气来做重构。
对于遗留系统,没出问题的地方,你不要动它。出问题的,修复并补充测试代码。如果更进一步,对于高危的功能和模块可以做定向增强。
招募优秀的工程师,好的作风和习惯可以影响整个团队。
抓住痛得不行的时候大作文章。平常说持续集成,可能团队成员没有感受到好处,出 bug 了,再回顾看看;copy-paste 代码总有一天不能满足更复杂的需求,这样从心理认同上,大家觉得必须重写了。抓住这样的机会,构建品质高一些的代码和质量防线。改变一个人是非常难的,抓住这样的机会很重要!
4. 内建质量
前文谈技术债务,谈反馈都提及到了持续集成。那么整体来讲,内建质量就是更短构建,更快反馈。
同时,除了正确的做事,还要思辨【做正确的事】。通过 ATDD / 引流对比 / 串讲反串讲 / 评审 / 录制回放验证 / 接口回归测试 等手段可以保证需求到设计、实现是否发生了变形。
内建质量 = 正确的做事 * 做正确的事
5. 不唯新、不唯上、实践是检验真理的标准
就技术选型而言,不迷信、不唯新、不唯上,建议尽早做小范围的可行性论证。比如好久前流行的 OSGI,上这个东西,获得了什么,风险是什么。引入一个新的技术栈就带来一层复杂度,而复杂度有一条天性。复杂度只能增加,不能减少,就像宇宙中的熵一样。就如同一个接口一旦开放使用,就很难把这个接口停用。一旦以一个高耦合的方式引入到一种技术,要调整或者去除,带来的成本是几何级数一样的。
总结,对于技术选型请看看成熟度、团队人员的掌握程度、收益和风险评估,一旦陷入泥潭则无异于梦魇之旅。要活得好好的,可以采取可行性论证,基于风险的架构设计。
6. 复杂的问题域:专项突破
在文章中提到过,问题域的复杂度会导致出 bug 的风险增加。
随着业务的发展,对于可用性的诉求也越来越高。比如你曾经是单机房部署、然后演变成同城异机房、最后又发展到异地机房。因为有异地机房,对于 RPC 的路由复杂度就增加了,我得知道是路由到那个机房的;同样的,在开发环境、线下测试环境要模拟几种部署的情况亦是有复杂度的。
再举一个例子,有一个系统作支付链路的规则决策,起初可能就 4 万行代码;后来增加到 8 万,现在又增加到 10 万。代码行增加了,该应用的职责增加了,也可能调用逻辑的运算复杂度。那么如何保持对外 API 的 TPS 不降低,RT 不降低?
每次 release 不仅要完成功能用例的构建,亦要完成性能的测试。
那么要如何破呢?对于第二个例子而言,除了构建功能回归测试环境,还需要有性能回归测试环境,以保障每次 release 的产品性能是没有降低的。对于多机房这个 case 而言,技术架构要关注可测试性、易用性。不仅仅考虑复杂度带来诸多依赖产品的修改成本,还要考虑易用性,如何测试,甚至是如何方便的,低侵入的测试。因为一旦从单机房到多机房了,问题的外延就翻倍了。
比如一个定时任务会不会同时在 2 个机房运行导致数据重复;机房切换时是否有缓存切换的问题导致业务不可用;数据复制延迟带来的不一致性对业务的影响;对于就得有数据复制的监控、缓存的监控、数据在全局的唯一性管理策略,业务兼容性方案等等。
7. Leader 的意识
一个公司的文化由创始人决定,而一个团队的品质追求往往由其 Leader 决定。Leader 不自觉的保护主义,报喜不报忧等做法会让这个问题变得严重。见微知著,一叶而知秋。
如果有足够的意识,依靠体力至少也能拿过 80 分。团队成员都是看 Leader。Leader 是看重质量,大家就看重;Leader 是欺瞒,他们就欺瞒,Leader 熟视无睹,这个团队就坏透了。:)
8. 创新解决方案
Perl 设计者在著作 programming perl 中提到:优秀的程序员具有三大美德:
懒惰、急躁和傲慢 (laziness, Impatience and Hubris)
为了减少总能量支出而不遗余力地努力的素质,就是懒惰.
忍受不了程序执行的低效就是急躁.
容不得对错误不管不顾就是傲慢.
要发扬程序员的美德,比如对于规则系统如果不能构建上游上千种场景,就可能思考新的解法;有人说做业务系统没啥追求和挑战,但是又有满目苍夷的 bug,小事不想做,大事做不来。其实【小事不小】!
---------- END ----------
本公众号编辑部维护读者群之研发管理群,邀请了坐馆老司机曲健、伟山、安晓辉、史海峰嘉宾等参与交流。加群请在公众号回复:研发管理群。
往期推荐:
技术琐话
以分布式设计、架构、体系思想为基础,兼论研发相关的点点滴滴,不限于代码、质量体系和研发管理。